#include <signal.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <fcntl.h>
#include <sys/wait.h>

#include "utils.h"

#define TMP_FILE_SUFFIX            ".XXXXXX"
#define TMP_FILE_SUFFIX_LEN        (sizeof(TMP_FILE_SUFFIX) - 1)
#define TMP_FILE_DEFAULT_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) // 0644

int32_t file_write_atomic_sync(const char *filename, const uint8_t *data, size_t len)
{
    int fd = -1;
    ssize_t n = 0;
    mode_t file_mode = TMP_FILE_DEFAULT_FILE_MODE;

    struct stat st = {};
    if (0 == stat(filename, &st))
    {
        file_mode = st.st_mode & 0777;
    }

#if 0
    fd = open(tmp_filename, O_WRONLY | O_CREAT | O_TRUNC, file_mode);
    if (fd < 0)
    {
        LOG_PRINT_ERROR("open fail, errno[%d](%s)\n", errno, strerror(errno));
        return -1;
    }
#else
    char tmp_filename[PATH_MAX] = {};
    if (strlen(filename) + TMP_FILE_SUFFIX_LEN >= sizeof(tmp_filename))
    {
        LOG_PRINT_ERROR("filename len[%zu] is limit\n", strlen(filename));
        return -1;
    }

    snprintf(tmp_filename, sizeof(tmp_filename), "%s%s", filename, TMP_FILE_SUFFIX);
    fd = mkstemp(tmp_filename);
    if (fd < 0)
    {
        LOG_PRINT_ERROR("mkstemp fail, errno[%d](%s)\n", errno, strerror(errno));
        return -1;
    }

    if (0 != fchmod(fd, file_mode))
    {
        LOG_PRINT_ERROR("fchmod fail, errno[%d](%s)\n", errno, strerror(errno));
        goto cleanup;
    }
#endif
    while (len > 0)
    {
        n = write(fd, data, len);
        if (n < 0)
        {
            LOG_PRINT_ERROR("write fail, errno[%d](%s)\n", errno, strerror(errno));
            if (errno == EINTR)
            {
                continue;
            }
            goto cleanup;
        }

        data = data + n;
        len -= (size_t)n;
    }

    if (0 != fsync(fd))
    {
        if (errno != ENOSYS)
        {
            LOG_PRINT_ERROR("fsync fail, errno[%d](%s)\n", errno, strerror(errno));
            goto cleanup;
        }
    }

    if (0 != close(fd))
    {
        LOG_PRINT_ERROR("close fail, errno[%d](%s)\n", errno, strerror(errno));
        goto cleanup;
    }
    fd = -1;

    if (0 != rename(tmp_filename, filename))
    {
        // rename not support EXDEV(different partition)
        LOG_PRINT_ERROR("rename fail, errno[%d](%s)\n", errno, strerror(errno));
        goto cleanup;
    }

    return 0;

cleanup:
    if (fd >= 0)
    {
        close(fd);
        fd = -1;
    }
    unlink(tmp_filename);
    return -1;
}

int32_t execute_command(const char *cmd)
{
    int32_t ret = 0;
    bool sigaction_flag = true;
    struct sigaction sa = {};
    struct sigaction old_sa = {};

    if (NULL == cmd)
    {
        LOG_PRINT_ERROR("invalid param\n");
        return -1;
    }

    LOG_PRINT_DEBUG("system command[%s]\n", cmd);

    sa.sa_handler = SIG_DFL;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;

    ret = sigaction(SIGCHLD, &sa, &old_sa);
    if (0 != ret)
    {
        sigaction_flag = false;
        LOG_PRINT_ERROR("sigaction set fail, ret[%d], errno[%d](%s)\n", ret, errno, strerror(errno));
        return -1;
    }

    int mr = system(cmd);
    if (-1 == mr)
    {
        LOG_PRINT_ERROR("system mr[%d], errno[%d](%s)\n", mr, errno, strerror(errno));
        ret = -1;
        goto label_execute_command_exit;
    }

    if (WIFEXITED(mr)) // exit success
    {
        ret = WEXITSTATUS(mr);
    }
    else // exit fail
    {
        LOG_PRINT_ERROR("system exit fail, WIFEXITED[%d]\n", WIFEXITED(mr));
        if (WIFSIGNALED(mr))
        {
            ret = WTERMSIG(mr);
        }
        else if (WIFSTOPPED(mr))
        {
            ret = WSTOPSIG(mr);
        }
        else
        {
            ret = -1;
            LOG_PRINT_ERROR("system exit fail\n");
        }
    }

label_execute_command_exit:
    if (sigaction_flag && 0 != sigaction(SIGCHLD, &old_sa, NULL))
    {
        LOG_PRINT_ERROR("sigaction recovery fail, errno[%d](%s)\n", errno, strerror(errno));
    }

    return ret;
}

/*
BITS 64
%define SYS_WRITE 1
%define SYS_EXIT 60
%define STDOUT 1

_start:
    ;; write
    mov rax, SYS_WRITE
    mov rdi, STDOUT
    lea rsi, [rel hello]
    mov rdx, hello_size
    syscall

    ret

hello: db "Hello, World", 10
hello_size: equ $-hello
*/

/*
// exec a program file(Machine code); like up⬆
    int fd = open(program_filepath, O_RDWR);
    assert(fd >= 0);

    struct stat statbuf;
    int err = fstat(fd, &statbuf);
    assert(err >= 0);

    // MAP_PRIVATE + PROT_EXEC mean not modify the ptr(SIGSEGV);
    void *ptr = mmap(NULL, statbuf.st_size,
                     PROT_EXEC,
                     MAP_PRIVATE,
                     fd, 0);
    assert(ptr != MAP_FAILED);
    close(fd);

    ((void (*)(void))ptr)(); // to function ptr to run! crazy!
*/

int32_t get_output_command(const char *cmd, uint8_t *output, size_t output_len)
{
    int32_t ret = 0;
    bool sigaction_flag = true;
    struct sigaction sa = {};
    struct sigaction old_sa = {};
    FILE *fp = NULL;

    if (NULL == cmd)
    {
        LOG_PRINT_ERROR("invalid param\n");
        return -1;
    }

    LOG_PRINT_DEBUG("get output command[%s]\n", cmd);

    sa.sa_handler = SIG_DFL;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;

    ret = sigaction(SIGCHLD, &sa, &old_sa);
    if (0 != ret)
    {
        sigaction_flag = false;
        LOG_PRINT_ERROR("sigaction set fail, ret[%d], errno[%d](%s)\n", ret, errno, strerror(errno));
        return -1;
    }

    fp = popen(cmd, "r");
    if (NULL == fp)
    {
        LOG_PRINT_ERROR("popen fail errno[%d](%s)\n", errno, strerror(errno));
        ret = -1;
        goto label_get_output_command_exit;
    }

    if (output && output_len > 0)
    {
        size_t total_read = 0;
        size_t n = 0;
        while (total_read < output_len)
        {
            n = fread(output + total_read, 1, output_len - total_read, fp);
            if (n == 0)
            {
                break; // EOF or error
            }
            total_read += n;
        }
    }

    int mr = pclose(fp);
    if (-1 == mr)
    {
        LOG_PRINT_ERROR("pclose fail, mr[%d], errno[%d](%s)\n", mr, errno, strerror(errno));
        ret = -1;
        goto label_get_output_command_exit;
    }

    if (WIFEXITED(mr)) // exit success
    {
        ret = WEXITSTATUS(mr);
    }
    else // exit fail
    {
        LOG_PRINT_ERROR("pclose exit fail, WIFEXITED[%d]\n", WIFEXITED(mr));
        if (WIFSIGNALED(mr))
        {
            ret = WTERMSIG(mr);
        }
        else if (WIFSTOPPED(mr))
        {
            ret = WSTOPSIG(mr);
        }
        else
        {
            ret = -1;
            LOG_PRINT_ERROR("pclose exit fail\n");
        }
    }

label_get_output_command_exit:
    if (sigaction_flag && 0 != sigaction(SIGCHLD, &old_sa, NULL))
    {
        LOG_PRINT_ERROR("sigaction recovery fail, errno[%d](%s)\n", errno, strerror(errno));
    }

    return ret;
}

/* This code is based on Stevens' Advanced Programming in the UNIX
 * Environment. */
bool daemonize(void)
{
    pid_t pid;

    /* Separate from our parent via fork, so init inherits us. */
    pid = fork();
    /* use _exit() to avoid triggering atexit() processing */
    switch (pid)
    {
        case -1:
            return false;
        case 0:
            break;
        default:
            _exit(0);
    }

    /* Session leader so ^C doesn't whack us. */
    if (setsid() == (pid_t)-1)
        return false;

    /* Move off any mount points we might be in. */
    if (chdir("/") != 0)
        return false;

    /* Discard our parent's old-fashioned umask prejudices. */
    umask(0);

#if 0
    /* Don't hold files open. */
    /* Not necessary: dup2() automatically closes the target fd. */
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
#endif

    /* Many routines write to stderr; that can cause chaos if used
     * for something else, so set it here. */
    int fd = open("/dev/null", O_RDWR);
    if (-1 == fd)
        return false;

    if (dup2(fd, STDIN_FILENO) == -1)
    {
        close(fd);
        return false;
    }

    if (dup2(fd, STDOUT_FILENO) == -1)
    {
        close(fd);
        return false;
    }

    if (dup2(fd, STDERR_FILENO) == -1)
    {
        close(fd);
        return false;
    }

    if (fd > STDERR_FILENO)
    {
        if (-1 == close(fd))
        {
            return false;
        }
    }

    return true;
}

int32_t get_exec_name(pid_t p_pid, char *p_exec_name, size_t p_exec_name_maxsize)
{
    char l_exe_link[PATH_MAX] = {0};
    char exec_name[PATH_MAX] = {0};
    char *l_name_ptr = NULL;

    memset(l_exe_link, 0, sizeof(l_exe_link));
    snprintf(l_exe_link, sizeof(l_exe_link) - 1, "/proc/%d/exe", p_pid);

    if (readlink(l_exe_link, exec_name, sizeof(exec_name) - 1) < 0)
    {
        return -1;
    }

    if ((l_name_ptr = strrchr(exec_name, '/')) == NULL)
    {
        return -1;
    }

    memset(p_exec_name, 0, p_exec_name_maxsize);
    strncpy(p_exec_name, l_name_ptr + 1, p_exec_name_maxsize - 1);

    return 0;
}